/*-------------------------------------------------------------------------
 * scram-common.c
 *		用于 SCRAM 身份验证的共享前端/后端代码
 *
 * 这包含了前端和后端实现加盐挑战响应身份验证机制（SCRAM）所需的常见底层函数， 根据 IETF 的 RFC 5802。
 *
 * Portions Copyright (c) 2017-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/common/scram-common.c
 *
 *-------------------------------------------------------------------------
 */
#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif

#include "common/base64.h"
#include "common/hmac.h"
#include "common/scram-common.h"
#include "port/pg_bswap.h"
#ifdef FDD
#include "common/md5.h"
#endif
/*
 * 计算 SaltedPassword。
 *
 * 密码应该已经通过 SASLprep 规范化。成功时返回 0，
 * 失败时返回 -1，并且 *errstr 指向关于错误细节的消息。
 */
int scram_SaltedPassword(const char *fc_password,
					 const char *fc_salt, int fc_saltlen, int fc_iterations,
					 uint8 *fc_result, const char **fc_errstr)
{
	int			fc_password_len = strlen(fc_password);
	uint32		fc_one = pg_hton32(1);
	int			fc_i,
				fc_j;
	uint8		fc_Ui[SCRAM_KEY_LEN];
	uint8		fc_Ui_prev[SCRAM_KEY_LEN];
	pg_hmac_ctx *fc_hmac_ctx = pg_hmac_create(PG_SHA256);

	if (fc_hmac_ctx == NULL)
	{
		*fc_errstr = pg_hmac_error(NULL);	/* 返回 OOM */
		return -1;
	}

	/*
	 * 使用给定的 salt 迭代 HMAC 条目的哈希计算。这本质上是 PBKDF2
	 * （参见 RFC2898），HMAC() 作为伪随机函数。
	 */

	/* 第一次迭代 */
	if (pg_hmac_init(fc_hmac_ctx, (uint8 *) fc_password, fc_password_len) < 0 ||
		pg_hmac_update(fc_hmac_ctx, (uint8 *) fc_salt, fc_saltlen) < 0 ||
		pg_hmac_update(fc_hmac_ctx, (uint8 *) &fc_one, sizeof(uint32)) < 0 ||
		pg_hmac_final(fc_hmac_ctx, fc_Ui_prev, sizeof(fc_Ui_prev)) < 0)
	{
		*fc_errstr = pg_hmac_error(fc_hmac_ctx);
		pg_hmac_free(fc_hmac_ctx);
		return -1;
	}

	memcpy(fc_result, fc_Ui_prev, SCRAM_KEY_LEN);

	/* 后续迭代 */
	for (fc_i = 2; fc_i <= fc_iterations; fc_i++)
	{
		if (pg_hmac_init(fc_hmac_ctx, (uint8 *) fc_password, fc_password_len) < 0 ||
			pg_hmac_update(fc_hmac_ctx, (uint8 *) fc_Ui_prev, SCRAM_KEY_LEN) < 0 ||
			pg_hmac_final(fc_hmac_ctx, fc_Ui, sizeof(fc_Ui)) < 0)
		{
			*fc_errstr = pg_hmac_error(fc_hmac_ctx);
			pg_hmac_free(fc_hmac_ctx);
			return -1;
		}

		for (fc_j = 0; fc_j < SCRAM_KEY_LEN; fc_j++)
			fc_result[fc_j] ^= fc_Ui[fc_j];
		memcpy(fc_Ui_prev, fc_Ui, SCRAM_KEY_LEN);
	}

	pg_hmac_free(fc_hmac_ctx);
	return 0;
}


/*
 * 计算 NULL 终止字符串的 SHA-256 哈希。（NULL 终止符不包含在哈希中）。
 * 成功时返回 0，失败时返回 -1，并且 *errstr 指向关于错误细节的消息。
 */
int scram_H(const uint8 *fc_input, int fc_len, uint8 *fc_result, const char **fc_errstr)
{
	pg_cryptohash_ctx *fc_ctx;

	fc_ctx = pg_cryptohash_create(PG_SHA256);
	if (fc_ctx == NULL)
	{
		*fc_errstr = pg_cryptohash_error(NULL);	/* 返回 OOM */
		return -1;
	}

	if (pg_cryptohash_init(fc_ctx) < 0 ||
		pg_cryptohash_update(fc_ctx, fc_input, fc_len) < 0 ||
		pg_cryptohash_final(fc_ctx, fc_result, SCRAM_KEY_LEN) < 0)
	{
		*fc_errstr = pg_cryptohash_error(fc_ctx);
		pg_cryptohash_free(fc_ctx);
		return -1;
	}

	pg_cryptohash_free(fc_ctx);
	return 0;
}

#ifdef FDD
/*
 * HMAC算法，来源于 scram_SaltedPassword 函数 .
 *
 * 用于将 password 和 salt 采用指定的摘要算法进行hamc计算，迭代 iterations次
 * 成功时返回 0，失败时返回 -1，并且 *errstr 指向关于错误细节的消息。
 */
int fdb_hmac(const uint8 *fc_password,int	fc_password_len, const uint8 *fc_salt, int fc_saltlen,
            int fc_iterations, pg_cryptohash_type fc_type,	 uint8 *fc_result, const char **fc_errstr)
{	
	uint32		fc_one = pg_hton32(1);
	int			fc_i,
				fc_j;
	uint8		fc_Ui[SCRAM_KEY_LEN];
	uint8		fc_Ui_prev[SCRAM_KEY_LEN];
	pg_hmac_ctx *fc_hmac_ctx = pg_hmac_create(fc_type);

	if (fc_hmac_ctx == NULL)
	{
		*fc_errstr = pg_hmac_error(NULL);	/* 返回 OOM */
		return -1;
	}

	/*
	 * 使用给定的 salt 迭代 HMAC 条目的哈希计算。这本质上是 PBKDF2
	 * （参见 RFC2898），HMAC() 作为伪随机函数。
	 */

	/* 第一次迭代 */
	if (pg_hmac_init(fc_hmac_ctx, (uint8 *) fc_password, fc_password_len) < 0 ||
		pg_hmac_update(fc_hmac_ctx, (uint8 *) fc_salt, fc_saltlen) < 0 ||
		pg_hmac_update(fc_hmac_ctx, (uint8 *) &fc_one, sizeof(uint32)) < 0 ||
		pg_hmac_final(fc_hmac_ctx, fc_Ui_prev, sizeof(fc_Ui_prev)) < 0)
	{
		*fc_errstr = pg_hmac_error(fc_hmac_ctx);
		pg_hmac_free(fc_hmac_ctx);
		return -1;
	}

	memcpy(fc_result, fc_Ui_prev, SCRAM_KEY_LEN);

	/* 后续迭代 */
	for (fc_i = 2; fc_i <= fc_iterations; fc_i++)
	{
		if (pg_hmac_init(fc_hmac_ctx, (uint8 *) fc_password, fc_password_len) < 0 ||
			pg_hmac_update(fc_hmac_ctx, (uint8 *) fc_Ui_prev, SCRAM_KEY_LEN) < 0 ||
			pg_hmac_final(fc_hmac_ctx, fc_Ui, sizeof(fc_Ui)) < 0)
		{
			*fc_errstr = pg_hmac_error(fc_hmac_ctx);
			pg_hmac_free(fc_hmac_ctx);
			return -1;
		}

		for (fc_j = 0; fc_j < SCRAM_KEY_LEN; fc_j++)
			fc_result[fc_j] ^= fc_Ui[fc_j];
		memcpy(fc_Ui_prev, fc_Ui, SCRAM_KEY_LEN);
	}

	pg_hmac_free(fc_hmac_ctx);
	return 0;
}


/*
 * 计算 NULL 终止字符串的哈希。（NULL 终止符不包含在哈希中）。
 * 成功时返回 0，失败时返回 -1，并且 *errstr 指向关于错误细节的消息。
 * SM3和sha256 摘要以二进制字符串形式返回（大小为SCRAM_KEY_LEN），而不是转换为 ASCII 十六进制。
 */
int fdb_cryptohash_binary(const uint8 *fc_input, int fc_len, pg_cryptohash_type fc_type, uint8 *fc_result, const char **fc_errstr)
{
	pg_cryptohash_ctx *fc_ctx;

	fc_ctx = pg_cryptohash_create(fc_type);
	if (fc_ctx == NULL)
	{
		*fc_errstr = pg_cryptohash_error(NULL);	/* 返回 OOM */
		return -1;
	}

	if (pg_cryptohash_init(fc_ctx) < 0 ||
		pg_cryptohash_update(fc_ctx, fc_input, fc_len) < 0 ||
		pg_cryptohash_final(fc_ctx, fc_result, SCRAM_KEY_LEN) < 0)
	{
		*fc_errstr = pg_cryptohash_error(fc_ctx);
		pg_cryptohash_free(fc_ctx);
		return -1;
	}

	pg_cryptohash_free(fc_ctx);
	return 0;
}

//result 为以 '\0' 结尾的十六进制数字字符串。SM3和sha256 摘要是 32*2 字节长的，每个字节由两个十六进制字符表示。因此，需要提供一个包含 32*2+1 个字符的数组，包括末尾的 '\0'。
int fdb_cryptohash(const uint8 *fc_input, int fc_len, pg_cryptohash_type fc_type,  char *fc_result, const char **fc_errstr)
{
	pg_cryptohash_ctx *fc_ctx;
	uint8		fc_sum[SCRAM_KEY_LEN];

	fc_ctx = pg_cryptohash_create(fc_type);
	if (fc_ctx == NULL)
	{
		*fc_errstr = pg_cryptohash_error(NULL);	/* 返回 OOM */
		return -1;
	}

	if (pg_cryptohash_init(fc_ctx) < 0 ||
		pg_cryptohash_update(fc_ctx, fc_input, fc_len) < 0 ||
		pg_cryptohash_final(fc_ctx, fc_sum, SCRAM_KEY_LEN) < 0)
	{
		*fc_errstr = pg_cryptohash_error(fc_ctx);
		pg_cryptohash_free(fc_ctx);
		return -1;
	}

	if(!bytesToHexs(fc_sum,SCRAM_KEY_LEN,fc_result,(SCRAM_KEY_LEN*2 + 1)))
	{
		*fc_errstr = _("bytesToHexs len invaid");
		pg_cryptohash_free(fc_ctx);
		return -1;
	}

	pg_cryptohash_free(fc_ctx);
	return 0;
}

#endif

/*
 * 计算 ClientKey。成功时返回 0，失败时返回 -1，并且 *errstr
 * 指向关于错误细节的消息。
 */
int scram_ClientKey(const uint8 *fc_salted_password, uint8 *fc_result,
				const char **fc_errstr)
{
	pg_hmac_ctx *fc_ctx = pg_hmac_create(PG_SHA256);

	if (fc_ctx == NULL)
	{
		*fc_errstr = pg_hmac_error(NULL);	/* 返回 OOM */
		return -1;
	}

	if (pg_hmac_init(fc_ctx, fc_salted_password, SCRAM_KEY_LEN) < 0 ||
		pg_hmac_update(fc_ctx, (uint8 *) "Client Key", strlen("Client Key")) < 0 ||
		pg_hmac_final(fc_ctx, fc_result, SCRAM_KEY_LEN) < 0)
	{
		*fc_errstr = pg_hmac_error(fc_ctx);
		pg_hmac_free(fc_ctx);
		return -1;
	}

	pg_hmac_free(fc_ctx);
	return 0;
}

/*
 * 计算 ServerKey。成功时返回 0，失败时返回 -1，并且 *errstr
 * 指向关于错误细节的消息。
 */
int scram_ServerKey(const uint8 *fc_salted_password, uint8 *fc_result,
				const char **fc_errstr)
{
	pg_hmac_ctx *fc_ctx = pg_hmac_create(PG_SHA256);

	if (fc_ctx == NULL)
	{
		*fc_errstr = pg_hmac_error(NULL);	/* 返回 OOM */
		return -1;
	}

	if (pg_hmac_init(fc_ctx, fc_salted_password, SCRAM_KEY_LEN) < 0 ||
		pg_hmac_update(fc_ctx, (uint8 *) "Server Key", strlen("Server Key")) < 0 ||
		pg_hmac_final(fc_ctx, fc_result, SCRAM_KEY_LEN) < 0)
	{
		*fc_errstr = pg_hmac_error(fc_ctx);
		pg_hmac_free(fc_ctx);
		return -1;
	}

	pg_hmac_free(fc_ctx);
	return 0;
}


/*
 * 构建 SCRAM 密钥，用于存储在 pg_authid.rolpassword 中。
 *
 * 如果需要，密码应该已经经过 SASLprep 处理！
 *
 * 如果 iterations 为 0，则使用默认的迭代次数。结果是
 * 通过 palloc 或 malloc 分配的，因此调用者需要负责释放它。
 *
 * 出错时返回 NULL，并将 *errstr 设置为指向关于错误细节的消息。
 */
char * scram_build_secret(const char *fc_salt, int fc_saltlen, int fc_iterations,
				   const char *fc_password, const char **errstr)
{
	uint8		fc_salted_password[SCRAM_KEY_LEN];
	uint8		fc_stored_key[SCRAM_KEY_LEN];
	uint8		fc_server_key[SCRAM_KEY_LEN];
	char	   *result;
	char	   *fc_p;
	int			maxlen;
	int			fc_encoded_salt_len;
	int			fc_encoded_stored_len;
	int			fc_encoded_server_len;
	int			fc_encoded_result;

	if (fc_iterations <= 0)
		fc_iterations = SCRAM_DEFAULT_ITERATIONS;

	/* 计算 StoredKey 和 ServerKey */
	if (scram_SaltedPassword(fc_password, fc_salt, fc_saltlen, fc_iterations,
							 fc_salted_password, errstr) < 0 ||
		scram_ClientKey(fc_salted_password, fc_stored_key, errstr) < 0 ||
		scram_H(fc_stored_key, SCRAM_KEY_LEN, fc_stored_key, errstr) < 0 ||
		scram_ServerKey(fc_salted_password, fc_server_key, errstr) < 0)
	{
		/* errstr 已在此填充 */
#ifdef FRONTEND
		return NULL;
#else
		elog(ERROR, "could not calculate stored key and server key: %s",
			 *errstr);
#endif
	}

	/*----------
	 * 格式为：
	 * SCRAM-SHA-256$<迭代次数>:<salt>$<StoredKey>:<ServerKey>
	 *----------
	 */
	fc_encoded_salt_len = pg_b64_enc_len(fc_saltlen);
	fc_encoded_stored_len = pg_b64_enc_len(SCRAM_KEY_LEN);
	fc_encoded_server_len = pg_b64_enc_len(SCRAM_KEY_LEN);

	maxlen = strlen("SCRAM-SHA-256") + 1
		+ 10 + 1				/* 迭代次数 */
		+ fc_encoded_salt_len + 1	/* Base64 编码的 salt */
		+ fc_encoded_stored_len + 1	/* Base64 编码的 StoredKey */
		+ fc_encoded_server_len + 1;	/* Base64 编码的 ServerKey */

#ifdef FRONTEND
	result = malloc(maxlen);
	if (!result)
	{
		*errstr = _("out of memory");
		return NULL;
	}
#else
	result = palloc(maxlen);
#endif

	fc_p = result + sprintf(result, "SCRAM-SHA-256$%d:", fc_iterations);

	/* salt */
	fc_encoded_result = pg_b64_encode(fc_salt, fc_saltlen, fc_p, fc_encoded_salt_len);
	if (fc_encoded_result < 0)
	{
		*errstr = _("could not encode salt");
#ifdef FRONTEND
		free(result);
		return NULL;
#else
		elog(ERROR, "%s", *errstr);
#endif
	}
	fc_p += fc_encoded_result;
	*(fc_p++) = '$';

	/* 存储密钥 */
	fc_encoded_result = pg_b64_encode((char *) fc_stored_key, SCRAM_KEY_LEN, fc_p,
								   fc_encoded_stored_len);
	if (fc_encoded_result < 0)
	{
		*errstr = _("could not encode stored key");
#ifdef FRONTEND
		free(result);
		return NULL;
#else
		elog(ERROR, "%s", *errstr);
#endif
	}

	fc_p += fc_encoded_result;
	*(fc_p++) = ':';

	/* 服务器密钥 */
	fc_encoded_result = pg_b64_encode((char *) fc_server_key, SCRAM_KEY_LEN, fc_p,
								   fc_encoded_server_len);
	if (fc_encoded_result < 0)
	{
		*errstr = _("could not encode server key");
#ifdef FRONTEND
		free(result);
		return NULL;
#else
		elog(ERROR, "%s", *errstr);
#endif
	}

	fc_p += fc_encoded_result;
	*(fc_p++) = '\0';

	Assert(fc_p - result <= maxlen);

	return result;
}
